昨天結尾說到要聊技術棧,今天就把 line-notion-bot 用到的五個服務攤開來看:Cloudflare Workers、OpenRouter、Mistral、Notion、Jina Reader。這系列文章其中一個目標本來就是全免費也能做出堪用的東西,但免費不代表沒有代價,每一個服務背後都藏著一點取捨,值得記下來。
Cloudflare Workers 這邊選得最沒有懸念。整支 bot 只有一個檔案 src/index.js,沒有框架、沒有建置流程,也沒有資料庫或 KV,所有「記憶」都存在 Notion 裡,每個 request 都是無狀態的。這樣寫最大的代價是部署完全手動,沒有 CI,改完程式碼要自己記得跑 wrangler deploy,查 log 也要自己開 wrangler tail 盯著看。對一支只服務自己一個人的 bot 來說,這個代價我覺得划算:省下維護框架、寫測試、接 CI 的時間,換來改一行程式碼、跑一次指令就上線的速度。
AI 這層一開始選 OpenRouter,是看中它整合各家免費模型的能力,但免費模型不穩定是出了名的,說被下架就下架。為了應付這件事,wrangler.toml 裡的 AI_MODELS 設成一個陣列:
AI_MODELS = ["meta-llama/llama-4-maverick:free", "meta-llama/llama-4-scout:free", "openrouter/free"]
OpenRouter 的 fallback 機制會按順序試,前面掛了換下一個,陣列最後固定放 openrouter/free(自動選型)當保底。這個陣列有個硬限制,最多只能放 3 個,超過的話 API 直接回 400。免費模型另一個麻煩是偶爾不照指示輸出 JSON,會回一些類似安全審查標記的字串,導致三層 JSON 解析全部失敗。錯誤畫面大概長這樣:

後來又得知 Mistral 也有免費額度可以用,就加了一個設計:有設定 MISTRAL_API_KEY 的話,AI 分類會優先打 Mistral 官方 API,用自己帳號的免費額度,失敗才退回 OpenRouter 的陣列。Mistral 不支援那種多模型 fallback,只能傳一個固定的 model 字串,跟 OpenRouter 那層是兩支獨立的呼叫函式。
Notion 選它單純是因為它同時是資料庫又是介面,存進去的筆記自己就能打開來看、整理,不用另外做一個後台。API 本身免費,Personal Access Token 建好也不用額外設定 Connections,會自動繼承帳號能存取的所有內容,省了一道手續。取捨主要出在它的篩選機制:select、multi_select 篩選如果給的值不是資料庫裡已經存在的選項,API 會直接回 400,就算包在 or 條件裡也一樣不會跳過。這個雷後面幾天寫搜尋功能時還會再遇到。
最後是 Jina Reader,拿來把任意網址轉成乾淨的純文字內容給 AI 讀。它免費,但不帶 API Key 的話額度是每分鐘 20 次,而且是跟全世界共用 Cloudflare Workers 的出口 IP,很容易被別人打光額度。註冊拿一組專屬 Key 之後額度變成每分鐘 100 次,這組 Key 本身也免費,等於花五分鐘註冊,換來穩定不少的額度,划算。
五個服務串起來,全程真的沒花一毛錢,但每一個免費背後都有限制在等著你撞上:陣列上限、篩選規則、共用 IP 額度,這些眉角先知道,後面就不會白繞路。明天開始正式進 line-notion-bot 的細節,先從 Notion 資料庫設計講起:欄位怎麼設計,其實就是在幫 AI 定輸出規格。